#include <iostream>
#include <memory>
#include <map>
#include <functional>

/*

https://stackoverflow.com/questions/36120424/alternatives-of-static-pointer-cast-for-unique-ptr

*/

class base_test_msg_t {

    public:
        base_test_msg_t() { std::cout << "base_test_msg_t constructor "<< std::endl; }
        virtual ~base_test_msg_t() { std::cout << "base_test_msg_t destructor"  << std::endl; }

};

class test_msg_t : public base_test_msg_t {

    public:
        test_msg_t() { std::cout << "test_msg_t constructor" << std::endl;}
        virtual ~test_msg_t() { std::cout<< "test_msg_t destructor" << std::endl;}
        
};

void test_case1() {

    test_msg_t * msg = new test_msg_t();
    delete msg;

    std::cout << "start smart pointer test" << std::endl;

    std::map<int, std::shared_ptr<test_msg_t>> test_map;
    std::shared_ptr<test_msg_t> ptr1 (new test_msg_t());
    test_map.insert( {1, ptr1});
    test_map.insert( {2, ptr1});

    std::cout << "referece count " << ptr1.use_count() << std::endl;

    // auto ptr2 = test_map.find(1);

    // test_map.erase(ptr2);

    std::cout << "referece count " << ptr1.use_count() << std::endl;

    // std::cout << "referece count " << ptr2->second.use_count() << std::endl;
   
    ptr1.reset();

    std::cout << "referece count " << ptr1.use_count() << std::endl;

    auto ptr2 = test_map.find(1);

    std::cout << "referece count " << ptr2->second.use_count() << std::endl;


    // std::shared_ptr<test_msg_t> ptr3 (new test_msg_t());
    // ptr3.reset();
    // ptr3.reset();
    // ptr3.reset();

    // std::cout << "referece count 3 " << ptr3.use_count() << std::endl;

    std::shared_ptr<test_msg_t> ptr4 (new test_msg_t(), [] (test_msg_t* p) {
        std::cout << "in registered destructor" << std::endl;
        delete p;
    });

}

void test_case2_shared_ptr_reset() {

    std::shared_ptr<test_msg_t> ptr1 (new test_msg_t());
    ptr1.reset();

    if (ptr1.get() == nullptr) {
        std::cout << "it becomes as a nullptr" << std::endl;
    }

}

void test_case3_unique_ptr() {

    std::unique_ptr<test_msg_t> ptr1 (new test_msg_t());

    test_msg_t* ptr2 = ptr1.release();

   if (ptr1.get() == nullptr)
       std::cout << "it becomes as a nullptr "  << std::endl;

    delete ptr2;

}

void test_case4_unique_ptr_deleter() {

    std::unique_ptr<test_msg_t, std::function<void(test_msg_t*) >> ptr1 (new test_msg_t(), [] (test_msg_t* p) {
        std::cout << "in registered destructor" << std::endl;
        delete p;
    });
}

void test_case5_unique_ptr_static_cast() {

    std::unique_ptr<test_msg_t> ptr1 (new test_msg_t());

    base_test_msg_t* ptr2 = static_cast<base_test_msg_t*>(ptr1.get());

    ptr2++;

}

class A_ {
    public:
    A_() {
        std::cout << "A_()" << std::endl;
    }
    ~A_() {
        std::cout << "~A_()" << std::endl;
    }
};
typedef std::shared_ptr<A_> A_Ptr;
std::unordered_map<int, A_Ptr> test_map;

void test_func1(A_Ptr& a_ptr) {
    std::cout << a_ptr.use_count() << std::endl;
}

void test_func2(A_Ptr a_ptr) {
    std::cout << a_ptr.use_count() << std::endl;
}

void test_func3(A_Ptr& a_ptr) {
    test_map.insert(std::make_pair(1, a_ptr));
}

void test_func4(A_Ptr& a_ptr) {
    std::cout << a_ptr.use_count() << std::endl;
}
void test_case6() {
    A_Ptr a_ptr(new A_());
    std::cout << a_ptr.use_count() << std::endl;
    test_func1(a_ptr);
    test_func2(a_ptr);
    std::cout << "before insert to map " << a_ptr.use_count() << std::endl;
    test_func3(a_ptr);
    std::cout << "after insert to map " << a_ptr.use_count() << std::endl;
    std::function<void(void)> f1 = bind(test_func2, a_ptr);
    std::cout << "after bind2 " << a_ptr.use_count() << std::endl;
    {
        std::function<void(void)> f3 = bind(test_func2, a_ptr);
        std::cout << "after bind2-f3 " << a_ptr.use_count() << std::endl;
    }
    std::cout << "exit bind2-f3 " << a_ptr.use_count() << std::endl;

    std::function<void(void)> f2 = bind(test_func4, a_ptr);
    std::cout << "after bind4 " << a_ptr.use_count() << std::endl;
    f1();
    f2();
}


void test_case7() {
    A_Ptr a_ptr(new A_());
    A_Ptr b_ptr;

    std::cout << a_ptr.use_count() << std::endl;
    std::cout << b_ptr.use_count() << std::endl;

    b_ptr = std::move(a_ptr);

    std::cout << a_ptr.use_count() << std::endl;
    std::cout << b_ptr.use_count() << std::endl;
}

int main() {

    //test_case3_unique_ptr();
    //test_case5_unique_ptr_static_cast();

    //test_case6();
    test_case7();
    std::cout << "finished test" << std::endl;
    return 0;
}